#!/usr/bin/env python3
from __future__ import annotations

import argparse
import copy
import json
import re
from pathlib import Path
from typing import Any


DEFAULT_CLAIMS_IDENTIFIED_PATH = Path("claims_identified.json")
DEFAULT_CLAIM_TYPES_PATH = Path("claims_identified_case_type.json")
DEFAULT_CLAIM_VIEW_PATH = Path("claim_identification_view.json")
DEFAULT_FACT_LEDGER_PATH = Path("Fact_Ledger_base.json")
DEFAULT_EVIDENCE_ACTIO_PATH = Path("evidence_actio_support.json")
DEFAULT_FACT_ACTIO_PATH = Path("fact_actio_support.json")
DEFAULT_OUTPUT_DIR = Path(".")

EVIDENCE_INDEX_PATTERN = re.compile(r"(E-\d{3})")
OUTPUT_FILENAME_PATTERN = re.compile(r"^C-\d{3}_claim_information\.json$")


def load_json(path: Path) -> Any:
    with path.open("r", encoding="utf-8") as file:
        return json.load(file)


def write_json(path: Path, payload: dict[str, Any]) -> None:
    path.write_text(
        json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )


def ensure_object(value: Any, label: str) -> dict[str, Any]:
    if not isinstance(value, dict):
        raise ValueError(f"{label} must be a JSON object.")
    return value


def ensure_array(value: Any, label: str) -> list[Any]:
    if not isinstance(value, list):
        raise ValueError(f"{label} must be a JSON array.")
    return value


def ensure_string(value: Any, label: str) -> str:
    if not isinstance(value, str) or not value:
        raise ValueError(f"{label} must be a non-empty string.")
    return value


def unique_strings(values: list[Any]) -> list[str]:
    seen: set[str] = set()
    result: list[str] = []
    for value in values:
        if not isinstance(value, str):
            continue
        if value in seen:
            continue
        seen.add(value)
        result.append(value)
    return result


def to_string_list(values: Any) -> list[str]:
    if not isinstance(values, list):
        return []
    return [str(value) for value in values if value is not None]


def parse_evidence_index(evidence_ref: str) -> str:
    match = EVIDENCE_INDEX_PATTERN.search(evidence_ref)
    if not match:
        raise ValueError(f"Could not extract evidence index from evidence_ref={evidence_ref!r}")
    return match.group(1)


def is_actio_pauliana_case(claim_type: str) -> bool:
    return "사해행위취소" in claim_type


def build_claim_type_index(claim_types_payload: dict[str, Any]) -> dict[str, str]:
    claim_types = ensure_array(claim_types_payload.get("claim_types"), "claims_identified_case_type.json.claim_types")
    index: dict[str, str] = {}
    for entry in claim_types:
        claim_type_entry = ensure_object(entry, "claim_types entry")
        claim_id = ensure_string(claim_type_entry.get("claim_id"), "claim_types[].claim_id")
        claim_type = ensure_string(claim_type_entry.get("claim_type"), "claim_types[].claim_type")
        if claim_id in index:
            raise ValueError(f"Duplicate claim_id in claims_identified_case_type.json: {claim_id}")
        index[claim_id] = claim_type
    return index


def build_claim_view_index(claim_view_payload: dict[str, Any]) -> dict[str, dict[str, Any]]:
    items = ensure_array(claim_view_payload.get("items"), "claim_identification_view.json.items")
    index: dict[str, dict[str, Any]] = {}
    for item in items:
        claim_item = ensure_object(item, "claim_identification_view item")
        fact_id = ensure_string(claim_item.get("fact_id"), "claim_identification_view.items[].fact_id")
        if fact_id in index:
            raise ValueError(f"Duplicate fact_id in claim_identification_view.json: {fact_id}")
        index[fact_id] = claim_item
    return index


def build_fact_ledger_index(fact_ledger_payload: list[Any]) -> dict[str, dict[str, Any]]:
    index: dict[str, dict[str, Any]] = {}
    for item in ensure_array(fact_ledger_payload, "Fact_Ledger_base.json"):
        ledger_item = ensure_object(item, "Fact_Ledger_base entry")
        fact_id = ensure_string(ledger_item.get("fact_id"), "Fact_Ledger_base[].fact_id")
        if fact_id in index:
            raise ValueError(f"Duplicate fact_id in Fact_Ledger_base.json: {fact_id}")
        index[fact_id] = ledger_item
    return index


def build_evidence_actio_index(evidence_payload: list[Any]) -> dict[str, Any]:
    index: dict[str, Any] = {}
    for item in ensure_array(evidence_payload, "evidence_actio_support.json"):
        evidence_item = ensure_object(item, "evidence_actio_support entry")
        evidence_index = ensure_string(evidence_item.get("evidence_index"), "evidence_actio_support[].evidence_index")
        if evidence_index in index:
            raise ValueError(f"Duplicate evidence_index in evidence_actio_support.json: {evidence_index}")
        index[evidence_index] = copy.deepcopy(evidence_item.get("actio_pauliana_support"))
    return index


def build_fact_actio_index(fact_actio_payload: list[Any]) -> dict[str, Any]:
    index: dict[str, Any] = {}
    for item in ensure_array(fact_actio_payload, "fact_actio_support.json"):
        fact_actio_item = ensure_object(item, "fact_actio_support entry")
        source_bo_id = ensure_string(fact_actio_item.get("source_bo_id"), "fact_actio_support[].source_bo_id")
        if source_bo_id in index:
            raise ValueError(f"Duplicate source_bo_id in fact_actio_support.json: {source_bo_id}")
        index[source_bo_id] = copy.deepcopy(fact_actio_item.get("actio_pauliana_support"))
    return index


def build_evidence_strength(payload: Any) -> dict[str, Any]:
    payload = payload if isinstance(payload, dict) else {}
    return {
        "has_direct_evidence": bool(payload.get("has_direct_evidence")),
        "has_corroboration": bool(payload.get("has_corroboration")),
        "authentication_status_summary": payload.get("authentication_status_summary"),
    }


def build_evidence_links(
    evidence_refs: list[str],
    include_actio_support: bool,
    evidence_actio_index: dict[str, Any],
) -> list[dict[str, Any]]:
    links: list[dict[str, Any]] = []
    for evidence_ref in evidence_refs:
        evidence_index = parse_evidence_index(evidence_ref)
        actio_support = None
        if include_actio_support:
            actio_support = copy.deepcopy(evidence_actio_index.get(evidence_index))
        links.append(
            {
                "evidence_index": evidence_index,
                "evidence_ref": evidence_ref,
                "actio_pauliana_support": actio_support,
            }
        )
    return links


def build_fact_actio_support_link(
    bo_id: str | None,
    include_actio_support: bool,
    fact_actio_index: dict[str, Any],
) -> dict[str, Any] | None:
    if not include_actio_support or not bo_id:
        return None
    if bo_id not in fact_actio_index:
        return None
    return {
        "source_bo_id": bo_id,
        "actio_pauliana_support": copy.deepcopy(fact_actio_index[bo_id]),
    }


def build_fact_item(
    fact_id: str,
    claim_view_index: dict[str, dict[str, Any]],
    fact_ledger_index: dict[str, dict[str, Any]],
    evidence_actio_index: dict[str, Any],
    fact_actio_index: dict[str, Any],
    include_actio_support: bool,
) -> dict[str, Any]:
    if fact_id not in claim_view_index:
        raise ValueError(f"fact_id {fact_id} was not found in claim_identification_view.json")

    claim_view_item = claim_view_index[fact_id]
    fact_ledger_item = fact_ledger_index.get(fact_id, {})

    evidence_refs = unique_strings(to_string_list(claim_view_item.get("evidence_refs")))
    bo_id = claim_view_item.get("bo_id")
    if bo_id is not None and not isinstance(bo_id, str):
        bo_id = str(bo_id)

    return {
        "fact_id": fact_id,
        "legal_calculation_object": copy.deepcopy(fact_ledger_item.get("legal_calculation_object")),
        "evidence_links": build_evidence_links(
            evidence_refs=evidence_refs,
            include_actio_support=include_actio_support,
            evidence_actio_index=evidence_actio_index,
        ),
        "bo_id": bo_id,
        "event_date": claim_view_item.get("event_date"),
        "action_text": claim_view_item.get("action_text"),
        "actors": unique_strings(to_string_list(claim_view_item.get("actors"))),
        "object_spec": claim_view_item.get("object_spec"),
        "amount": claim_view_item.get("amount"),
        "claim_event_type": claim_view_item.get("claim_event_type"),
        "claim_nature_tags": unique_strings(to_string_list(claim_view_item.get("claim_nature_tags"))),
        "claim_creditor": claim_view_item.get("claim_creditor"),
        "claim_debtor": claim_view_item.get("claim_debtor"),
        "legal_keywords": unique_strings(to_string_list(claim_view_item.get("legal_keywords"))),
        "evidence_refs": evidence_refs,
        "evidence_strength": build_evidence_strength(claim_view_item.get("evidence_strength")),
        "fact_actio_support": build_fact_actio_support_link(
            bo_id=bo_id,
            include_actio_support=include_actio_support,
            fact_actio_index=fact_actio_index,
        ),
    }


def build_claim_payload(
    claim_entry: dict[str, Any],
    claim_type: str,
    claim_view_index: dict[str, dict[str, Any]],
    fact_ledger_index: dict[str, dict[str, Any]],
    evidence_actio_index: dict[str, Any],
    fact_actio_index: dict[str, Any],
) -> dict[str, Any]:
    claim_id = ensure_string(claim_entry.get("claim_id"), "claims_identified.json.identified_claims[].claim_id")
    include_actio_support = is_actio_pauliana_case(claim_type)
    source_fact_ids = unique_strings(to_string_list(claim_entry.get("source_fact_ids")))

    facts = [
        build_fact_item(
            fact_id=fact_id,
            claim_view_index=claim_view_index,
            fact_ledger_index=fact_ledger_index,
            evidence_actio_index=evidence_actio_index,
            fact_actio_index=fact_actio_index,
            include_actio_support=include_actio_support,
        )
        for fact_id in source_fact_ids
    ]

    return {
        "claim_id": claim_id,
        "claim_type": claim_type,
        "plaintiffs": unique_strings(to_string_list(claim_entry.get("plaintiffs"))),
        "defendants": unique_strings(to_string_list(claim_entry.get("defendants"))),
        "claim_statement": claim_entry.get("claim_statement"),
        "source_fact_ids": source_fact_ids,
        "review_flags": unique_strings(to_string_list(claim_entry.get("review_flags"))),
        "facts": facts,
    }


def remove_stale_outputs(output_dir: Path, live_claim_ids: set[str]) -> None:
    for path in output_dir.glob("C-*_claim_information.json"):
        if not OUTPUT_FILENAME_PATTERN.match(path.name):
            continue
        claim_id = path.name.removesuffix("_claim_information.json")
        if claim_id in live_claim_ids:
            continue
        path.unlink()


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Generate one C-###_claim_information.json file per claim_id."
    )
    parser.add_argument("--claims-identified", type=Path, default=DEFAULT_CLAIMS_IDENTIFIED_PATH)
    parser.add_argument("--claim-types", type=Path, default=DEFAULT_CLAIM_TYPES_PATH)
    parser.add_argument("--claim-view", type=Path, default=DEFAULT_CLAIM_VIEW_PATH)
    parser.add_argument("--fact-ledger", type=Path, default=DEFAULT_FACT_LEDGER_PATH)
    parser.add_argument("--evidence-actio-support", type=Path, default=DEFAULT_EVIDENCE_ACTIO_PATH)
    parser.add_argument("--fact-actio-support", type=Path, default=DEFAULT_FACT_ACTIO_PATH)
    parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR)
    return parser.parse_args()


def main() -> None:
    args = parse_args()

    claims_identified = ensure_object(load_json(args.claims_identified), "claims_identified.json")
    claims_identified_entries = ensure_array(
        claims_identified.get("identified_claims"),
        "claims_identified.json.identified_claims",
    )
    claim_type_index = build_claim_type_index(ensure_object(load_json(args.claim_types), "claims_identified_case_type.json"))
    claim_view_index = build_claim_view_index(ensure_object(load_json(args.claim_view), "claim_identification_view.json"))
    fact_ledger_index = build_fact_ledger_index(load_json(args.fact_ledger))
    evidence_actio_index = build_evidence_actio_index(load_json(args.evidence_actio_support))
    fact_actio_index = build_fact_actio_index(load_json(args.fact_actio_support))

    args.output_dir.mkdir(parents=True, exist_ok=True)

    claim_ids = {
        ensure_string(entry.get("claim_id"), "claims_identified.json.identified_claims[].claim_id")
        for entry in claims_identified_entries
    }
    remove_stale_outputs(args.output_dir, claim_ids)

    written_count = 0
    for raw_entry in claims_identified_entries:
        claim_entry = ensure_object(raw_entry, "claims_identified.json identified_claim entry")
        claim_id = ensure_string(claim_entry.get("claim_id"), "claims_identified.json.identified_claims[].claim_id")
        if claim_id not in claim_type_index:
            raise ValueError(f"claim_id {claim_id} was not found in claims_identified_case_type.json")

        claim_payload = build_claim_payload(
            claim_entry=claim_entry,
            claim_type=claim_type_index[claim_id],
            claim_view_index=claim_view_index,
            fact_ledger_index=fact_ledger_index,
            evidence_actio_index=evidence_actio_index,
            fact_actio_index=fact_actio_index,
        )
        output_path = args.output_dir / f"{claim_id}_claim_information.json"
        write_json(output_path, claim_payload)
        written_count += 1

    if written_count != len(claim_ids):
        raise RuntimeError(
            f"Expected to write {len(claim_ids)} claim files, but wrote {written_count}."
        )


if __name__ == "__main__":
    main()
